Udforsk TypeScript Template Literal Typer og byg en runtime valideringsmotor for robust strengverifikation og typesikkerhed. Lær at forhindre fejl ved at validere strenge mod dine definerede template literal typer ved runtime.
TypeScript Template Literal Valideringsmotor: Runtime String Verifikation
TypeScripts template literal typer tilbyder kraftfuld compile-time strengmanipulation og typesikkerhed. Disse tjek er dog begrænset til compile-time. Dette blogindlæg udforsker, hvordan man bygger en runtime valideringsmotor for TypeScript template literal typer, hvilket muliggør robust strengverifikation og forhindrer potentielle fejl under programmets eksekvering.
Introduktion til TypeScript Template Literal Typer
Template literal typer giver dig mulighed for at definere specifikke strengformer baseret på literale værdier, unions og typeinferens. Dette muliggør præcis typekontrol og auto-fuldførelse, hvilket er særligt nyttigt, når man arbejder med struktureret data eller domænespecifikke sprog.
Overvej for eksempel en type til at repræsentere valutakoder:
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const validCurrency: FormattedCurrencyString = "USD-100"; // OK
const invalidCurrency: FormattedCurrencyString = "CAD-50"; // Typefejl ved compile-time
Dette eksempel viser, hvordan TypeScript håndhæver FormattedCurrencyString-typen ved compile-time. Men hvis valutakoden kommer fra en ekstern kilde (f.eks. brugerinput, API-svar), har du brug for runtime-validering for at sikre typesikkerhed.
Behovet for Runtime Validering
Selvom TypeScript giver fremragende compile-time typekontrol, kan det ikke garantere gyldigheden af data modtaget fra eksterne kilder ved runtime. At stole udelukkende på compile-time typer kan føre til uventede fejl og sårbarheder.
Overvej følgende scenarie:
function processCurrency(currencyString: FormattedCurrencyString) {
// ... en logik der antager, at strengen er korrekt formateret
}
const userInput = "CAD-50"; // Antag at dette kommer fra brugerinput
// Dette vil kompilere, men vil forårsage en runtime-fejl, hvis logikken indeni
// `processCurrency` er afhængig af formatet.
processCurrency(userInput as FormattedCurrencyString);
I dette tilfælde caster vi userInput til FormattedCurrencyString og omgår dermed TypeScripts compile-time tjek. Hvis processCurrency er afhængig af, at strengen er formateret korrekt, vil den støde på en runtime-fejl.
Runtime validering bygger bro over dette hul ved at verificere, at de data, der modtages ved runtime, overholder de forventede TypeScript-typer.
Opbygning af en Template Literal Valideringsmotor
Vi kan bygge en runtime valideringsmotor ved hjælp af regulære udtryk og TypeScripts typesystem. Motoren vil tage en template literal type og en streng som input og returnere, om strengen matcher typen.
Trin 1: Definition af en Type til Runtime Validering
Først har vi brug for en generisk type, der kan repræsentere runtime-ækvivalenten af en template literal type. Denne type skal kunne håndtere forskellige slags template literals, herunder literaler, unions og typeparametre.
type TemplateLiteralToRegex =
T extends `${infer Start}${infer Middle}${infer End}`
? Start extends string
? Middle extends string
? End extends string
? TemplateLiteralToRegexStart & TemplateLiteralToRegexMiddle & TemplateLiteralToRegex
: never
: never
: never
: TemplateLiteralToRegexStart;
type TemplateLiteralToRegexStart = T extends `${infer Literal}` ? Literal : string;
type TemplateLiteralToRegexMiddle = T extends `${infer Literal}` ? Literal : string;
Denne rekursive typedefinition nedbryder template literal'en i dens bestanddele og konverterer hver del til et mønster for et regulært udtryk.
Trin 2: Implementering af Valideringsfunktionen
Dernæst implementerer vi valideringsfunktionen, der tager template literal typen og den streng, der skal valideres, som input. Denne funktion bruger det regulære udtryk genereret af TemplateLiteralToRegex til at teste strengen.
function isValid(str: string, templateType: T): boolean {
const regexPattern = `^${convertTemplateLiteralToRegex(templateType)}$`;
const regex = new RegExp(regexPattern);
return regex.test(str);
}
function convertTemplateLiteralToRegex(templateType: T): string {
// Grundlæggende konvertering for literale strenge - udvid dette for mere komplekse scenarier
return templateType.replace(/[.*+?^${}()|[\]]/g, '\\$&'); // Escape specielle regex-tegn
}
Denne funktion escaper specielle tegn i regulære udtryk og opretter et regulært udtryk fra template literal typen, og tester derefter strengen mod det regulære udtryk.
Trin 3: Brug af Valideringsmotoren
Nu kan du bruge isValid-funktionen til at validere strenge mod dine template literal typer ved runtime.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const userInput1 = "USD-100";
const userInput2 = "CAD-50";
console.log(`'${userInput1}' er gyldig: ${isValid(userInput1, "USD-100" )}`); // true
console.log(`'${userInput2}' er gyldig: ${isValid(userInput2, "USD-100")}`); // false
console.log(`'${userInput1}' er gyldig: ${isValid(userInput1, `USD-${100}`)}`); // true
console.log(`'${userInput2}' er gyldig: ${isValid(userInput2, `USD-${100}`)}`); // false
Dette eksempel viser, hvordan man bruger isValid-funktionen til at validere brugerinput mod FormattedCurrencyString-typen. Outputtet vil vise, om inputstrengene betragtes som gyldige eller ej, baseret på den specificerede template literal.
Avancerede Valideringsscenarier
Den grundlæggende valideringsmotor kan udvides til at håndtere mere komplekse scenarier, såsom unions, betingede typer og rekursive typer.
Håndtering af Unions
For at håndtere unions kan du ændre TemplateLiteralToRegex-typen til at generere et regulært udtryk, der matcher ethvert af union-medlemmerne.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
function isValidCurrencyCode(str: string, templateType: T): boolean {
const currencyCodes: CurrencyCode[] = ["USD", "EUR", "GBP"];
return currencyCodes.includes(str as CurrencyCode);
}
function isValidUnionFormattedCurrencyString(str: string): boolean {
const parts = str.split('-');
if(parts.length !== 2) return false;
const [currencyCode, amount] = parts;
if (!isValidCurrencyCode(currencyCode, currencyCode)) return false;
if (isNaN(Number(amount))) return false;
return true;
}
console.log(`'USD-100' er en gyldig formateret streng: ${isValidUnionFormattedCurrencyString('USD-100')}`);
console.log(`'CAD-50' er en gyldig formateret streng: ${isValidUnionFormattedCurrencyString('CAD-50')}`);
Håndtering af Betingede Typer
Betingede typer kan håndteres ved at evaluere betingelsen ved runtime og generere forskellige regulære udtryk baseret på resultatet.
type IsString = T extends string ? true : false;
// Dette eksempel kræver mere avanceret logik og er ikke fuldt implementerbart med simpel regex.
// Runtime type guards tilbyder en mere robust løsning i dette specifikke scenarie.
// Nedenstående kode er illustrativ og ville kræve tilpasning for at håndtere komplekse betingede typer.
function isString(value: any): value is string {
return typeof value === 'string';
}
function isValidConditionalType(value: any): boolean {
return isString(value);
}
console.log(`'hello' er en streng: ${isValidConditionalType('hello')}`);
console.log(`123 er en streng: ${isValidConditionalType(123)}`);
Håndtering af Rekursive Typer
Rekursive typer kan håndteres ved at definere en rekursiv funktion, der genererer mønsteret for det regulære udtryk. Vær dog forsigtig med at undgå uendelig rekursion og stack overflow-fejl. For dyb rekursion er iterative tilgange med passende grænser afgørende.
Alternativer til Regulære Udtryk
Selvom regulære udtryk er et kraftfuldt værktøj til strengvalidering, kan de være komplekse og svære at vedligeholde. Andre tilgange til runtime validering inkluderer:
- Brugerdefinerede Valideringsfunktioner: Skriv brugerdefinerede funktioner til at validere specifikke typer baseret på din applikations krav.
- Type Guards: Brug 'type guards' til at indsnævre typen af en variabel ved runtime.
- Valideringsbiblioteker: Udnyt eksisterende valideringsbiblioteker som Zod eller Yup til at forenkle valideringsprocessen.
Zod, for eksempel, tilbyder en skema-baseret erklæring, der oversættes til en validering ved runtime:
import { z } from 'zod';
const CurrencyCodeSchema = z.enum(['USD', 'EUR', 'GBP']);
const FormattedCurrencyStringSchema = z.string().regex(new RegExp(`^${CurrencyCodeSchema.enum.USD}|${CurrencyCodeSchema.enum.EUR}|${CurrencyCodeSchema.enum.GBP}-[0-9]+$`));
try {
const validCurrency = FormattedCurrencyStringSchema.parse("USD-100");
console.log("Gyldig valuta:", validCurrency);
} catch (error) {
console.error("Ugyldig valuta:", error);
}
try {
const invalidCurrency = FormattedCurrencyStringSchema.parse("CAD-50");
console.log("Gyldig valuta:", invalidCurrency); //Dette vil ikke blive eksekveret, hvis parse fejler.
} catch (error) {
console.error("Ugyldig valuta:", error);
}
Bedste Praksis for Runtime Validering
Når du implementerer runtime validering, skal du huske følgende bedste praksis:
- Valider ved Grænsen: Valider data, så snart de kommer ind i dit system (f.eks. brugerinput, API-svar).
- Giv Klare Fejlmeddelelser: Generer informative fejlmeddelelser for at hjælpe brugere med at forstå, hvorfor deres input er ugyldigt.
- Brug en Konsistent Valideringsstrategi: Anvend en konsistent valideringsstrategi på tværs af din applikation for at sikre dataintegritet.
- Test Din Valideringslogik: Test din valideringslogik grundigt for at sikre, at den korrekt identificerer gyldige og ugyldige data.
- Balancer Ydeevne og Sikkerhed: Optimer din valideringslogik for ydeevne, mens du sikrer, at den effektivt forhindrer sikkerhedssårbarheder. Undgå alt for komplekse regex, der fører til denial of service.
Overvejelser omkring Internationalisering
Når man arbejder med strengvalidering i en global kontekst, skal man overveje internationalisering (i18n) og lokalisering (l10n). Forskellige locales kan have forskellige regler for formatering af strenge, såsom datoer, tal og valutaværdier.
For eksempel kan valutasymbolet for Euro (€) vises før eller efter beløbet, afhængigt af locale. Tilsvarende kan decimaladskilleren være et punktum (.) eller et komma (,).
For at håndtere disse variationer kan du bruge internationaliseringsbiblioteker som Intl, der leverer API'er til formatering og parsing af locale-følsomme data. For eksempel kan du tilpasse det tidligere eksempel til at håndtere forskellige valutaformater:
function isValidCurrencyString(currencyString: string, locale: string): boolean {
try {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyString.substring(0,3) }); //Meget grundlæggende eksempel
//Forsøg at parse valutaen ved hjælp af formatter. Dette eksempel er bevidst meget simpelt.
return true;
} catch (error) {
return false;
}
}
console.log(`USD-100 er gyldig for en-US: ${isValidCurrencyString('USD-100', 'en-US')}`);
console.log(`EUR-100 er gyldig for fr-FR: ${isValidCurrencyString('EUR-100', 'fr-FR')}`);
Dette kodestykke giver et grundlæggende eksempel. Korrekt internationalisering kræver mere grundig håndtering, potentielt ved brug af eksterne biblioteker eller API'er, der er specifikt designet til valutaformatering og -validering på tværs af forskellige locales.
Konklusion
Runtime validering er en essentiel del af at bygge robuste og pålidelige TypeScript-applikationer. Ved at kombinere TypeScripts template literal typer med regulære udtryk eller alternative valideringsmetoder kan du skabe en kraftfuld motor til at verificere gyldigheden af strenge ved runtime.
Denne tilgang forbedrer typesikkerheden, forhindrer uventede fejl og forbedrer den overordnede kvalitet af din kode. Efterhånden som du bygger mere komplekse applikationer, bør du overveje at inkorporere runtime validering for at sikre, at dine data overholder de forventede typer og formater.
Videre Udforskning
- Udforsk avancerede teknikker med regulære udtryk for mere komplekse valideringsscenarier.
- Undersøg valideringsbiblioteker som Zod og Yup for skema-baseret validering.
- Overvej at bruge kodegenereringsteknikker til automatisk at generere valideringsfunktioner fra TypeScript-typer.
- Studer internationaliseringsbiblioteker og API'er for at håndtere locale-følsomme data.